package com.spider.im.common.security.filter;

import cn.hutool.core.bean.BeanUtil;
import com.spider.im.common.aspect.BusinessException;
import com.spider.im.common.constant.RespCodeEnum;
import com.spider.im.common.security.JwtUtil;
import com.spider.im.login.resp.LoginUser;
import com.spider.im.user.entity.User;
import com.spider.im.user.resp.UserResp;
import com.spider.im.user.service.UserService;
import io.jsonwebtoken.ExpiredJwtException;
import io.netty.util.internal.ThrowableUtil;
import jakarta.annotation.Resource;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.List;

/**
 * 过滤器用于验证 JWT Token 并设置 Spring Security 上下文中的认证信息
 */
@Slf4j
@Component
public class JwtRequestFilter extends OncePerRequestFilter {

    @Resource
    private JwtUtil jwtUtil;

    @Resource
    private UserService userService;

    @Resource
    private UserDetailsService userDetailsService;

    // List of URLs that should not be checked for authentication
    @Value("#{'${no.authorization.path}'.split(',')}")
    private  List<String> excludedUrls;

    /**
     * 重写 doFilterInternal 方法以实现 JWT Token 的验证逻辑
     *
     * @param request  HTTP 请求对象
     * @param response HTTP 响应对象
     * @param chain    过滤器链
     * @throws ServletException 如果发生 Servlet 异常
     * @throws IOException      如果发生 I/O 异常
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {

        try {
            if (isExcludedUrl(request)) {
                chain.doFilter(request, response);
                return;
            }
            // 获取请求头中的 Authorization 信息
            final String authorizationHeader = request.getHeader("Authorization");

            String userId = null;
            String jwt = null;

            // 检查 Authorization 头部是否以 "Bearer " 开头
            if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
                // 提取 JWT Token
                jwt = authorizationHeader.substring(7);
                userId = extractUsernameFromJwt(jwt, request);
            } else {
                log.warn("JWT Token does not begin with Bearer String. Request Path: {}, IP Address: {} authorizationHeader: {}",
                        request.getRequestURI(), request.getRemoteAddr(),authorizationHeader);
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                return;
            }

            // 如果用户名不为空且当前上下文中没有认证信息，则验证 Token
            if (userId != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = loadUserDetails(userId, request);
                LoginUser loginUser = (LoginUser) userDetails;
                if (jwtUtil.validateToken(jwt, loginUser)) {
                    setAuthenticationContext(loginUser, request);
                } else {
                    // 处理 Token 无效的情况
                    log.error("Invalid JWT Token. Request Path: {}, IP Address: {},authorizationHeader:{}",request.getRequestURI(), request.getRemoteAddr(), authorizationHeader);
                    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                    return;
                }
            }
            // 继续执行过滤器链中的下一个过滤器
            chain.doFilter(request, response);
        } catch (BusinessException e) {
            log.error("Business exception occurred: {}", e.getMessage());
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        } catch (Exception e) {
            log.error("Unexpected error occurred: {}", ThrowableUtil.stackTraceToString(e));
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
    }

    /**
     * 检查请求路径是否在排除列表中（增强路径匹配逻辑）
     * @param request
     * @return
     */
    private boolean isExcludedUrl(HttpServletRequest request) {
        String requestUri = request.getRequestURI();
        if (requestUri == null || excludedUrls == null || excludedUrls.isEmpty()) {
            return false;
        }

        // 统一处理 URI，去除重复斜杠并标准化
        String normalizedPath = normalizePath(requestUri);

        return excludedUrls.stream()
                .filter(excluded -> excluded != null && !excluded.isEmpty())
                .anyMatch(excluded -> matchUri(normalizedPath, excluded));
    }

    /**
     * 标准化路径：去除重复斜杠，统一结尾斜杠处理
     */
    private String normalizePath(String path) {
        // 替换多个连续斜杠为单个
        String normalized = path.replaceAll("/+", "/");
        // 如果不是根路径且以 / 结尾，则去掉末尾斜杠
        if (normalized.length() > 1 && normalized.endsWith("/")) {
            normalized = normalized.substring(0, normalized.length() - 1);
        }
        return normalized;
    }

    /**
     * 判断指定路径是否匹配给定的排除路径规则
     */
    private boolean matchUri(String requestUri, String excluded) {
        // 再次统一处理 excluded 路径
        excluded = excluded.replaceAll("/+", "/");
        if (excluded.endsWith("/**")) {
            // 支持类似 Spring 的 /** 通配语法，匹配任意子路径
            String prefix = excluded.substring(0, excluded.length() - 3);
            return requestUri.equals(prefix) || requestUri.startsWith(prefix + "/");
        } else if (excluded.endsWith("/")) {
            excluded = excluded.substring(0, excluded.length() - 1);
        }
        return requestUri.equals(excluded) || requestUri.startsWith(excluded + "/");
    }

    /**
     * 从 JWT Token 中提取用户名，并处理可能的异常
     *
     * @param jwt    JWT Token
     * @param request HTTP 请求对象
     * @return 用户名
     */
    private String extractUsernameFromJwt(String jwt, HttpServletRequest request) {
        try {
            return jwtUtil.extractUsername(jwt);
        } catch (IllegalArgumentException e) {
            log.warn("Unable to get JWT Token. Request Path: {}, IP Address: {}",
                    request.getRequestURI(), request.getRemoteAddr());
            throw new BusinessException(RespCodeEnum.USER_NOT_LOGIN);
        } catch (ExpiredJwtException e) {
            log.warn("JWT Token has expired. Request Path: {}, IP Address: {}",
                    request.getRequestURI(), request.getRemoteAddr());
            throw new BusinessException(RespCodeEnum.USER_NOT_LOGIN);
        }  catch (Exception e) {
            log.warn("Invalid or expired token. Request Path: {}, IP Address: {}",
                    request.getRequestURI(), request.getRemoteAddr());
            throw new BusinessException(RespCodeEnum.USER_NOT_LOGIN);
        }
    }

    /**
     * 加载用户详细信息，并处理可能的异常
     *
     * @param userId 用户id
     * @param request  HTTP 请求对象
     * @return 用户详细信息
     */
    private UserDetails loadUserDetails(String userId, HttpServletRequest request) {
        try {
            User user = userService.getById(Long.valueOf(userId));
            UserResp userResp = new UserResp();
            BeanUtil.copyProperties(user, userResp);
            return new LoginUser(userResp);
        } catch (Exception e) {
            log.error("Error loading user details for username: {}. Request Path: {}, IP Address: {}",
                    userId, request.getRequestURI(), request.getRemoteAddr());
            throw new BusinessException(RespCodeEnum.USER_NOT_FOUND);
        }
    }

    /**
     * 设置 Spring Security 认证上下文
     *
     * @param userDetails 用户详细信息
     * @param request     HTTP 请求对象
     */
    private void setAuthenticationContext(UserDetails userDetails, HttpServletRequest request) {
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
                userDetails, null, userDetails.getAuthorities());
        token.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        SecurityContextHolder.getContext().setAuthentication(token);
    }
}
